package YAVMM::CLI;
use Moose;

with 'MooseX::Object::Pluggable';

use feature qw(switch);

use Dancer qw(:moose !options);

use MooseX::Method::Signatures;

use Term::ReadKey;
use Pod::Usage;
#use Log::Dispatch::Config;

use YAVMM::Error;
use YAVMM::Error::RPC;
use YAVMM::Error::Parameter;
#use YAVMM::Log::Dispatch::Config;

use LWP::UserAgent;
use HTTP::Cookies;
use HTTP::Request::Common;
use URI;
#use JSON qw(encode_json decode_json);

use TryCatch;

our $VERSION = '0.01';

has 'settings' => (
    is        => 'rw',
    does      => 'YAVMM::Settings',
    required  => 1,
);

has 'options' => (
    is         => 'rw',
    isa        => 'HashRef',
    default    => sub { {} },
);

has 'argv'    => (
    is         => 'rw',
    isa        => 'ArrayRef',
    default    => sub { [] },
);

has 'log' => (
    is         => 'rw',
    lazy_build => 1,
);

has '_uri' => (
    is         => 'ro',
    isa        => 'Str',
    lazy_build => 1,
);

has '_proxy' => (
    is         => 'ro',
    isa        => 'Str',
    lazy_build => 1,
);

has '_ua' => (
    is         => 'rw',
    lazy_build => 1,
);

sub help {
    my $self = shift;

    $self->_help(1);
}

sub _debug {
    return $_[0]->options->{debug};
}

sub _verbose {
    return $_[0]->options->{verbose};
}

sub _build__proxy {
    my $self = shift;

    my $ssl  = $self->_ssl;
    my $host = $self->_host;
    my $port = $self->_port;

    my $proxy = 'http';
    $proxy .= 's' if $ssl;
    $proxy .= "://";
    $proxy .= $self->_username.':'.$self->_password.'@',
    $proxy .= "$host:$port";
    $proxy .= "?session=yavmmrpc";

    return $proxy;

}

sub _username {
    my $self = shift;

    return $self->options->{username} if $self->options->{username};

    print 'User name: ';
    $self->options->{username} = ReadLine(0);
    chomp $self->options->{username};

    return $self->options->{username};
}

sub _password {
    my $self = shift;

    return $self->options->{password} if $self->options->{password};

    print 'Password: ';
    ReadMode('noecho');

    $self->options->{password} = ReadLine(0);
    chomp $self->options->{password};

    ReadMode('normal');

    return $self->options->{password};
}

sub _help {
    my $self      = shift;
    my $verbosity = shift;

    $verbosity = 0 if not defined $verbosity;

    pod2usage({
	-exitval => 1,
	-verbose => $verbosity,
    });
}

sub _build__uri {
    my $self = shift;

    my $ssl  = $self->_ssl;
    my $host = $self->_host;
    my $port = $self->_port;

    my $url = 'http';
    $url .= 's' if $ssl;
    $url .= "://";
#    $url .= $self->username.':'.$self->password.'@',
    $url .= "$host:$port";

    return $url;
}

sub _build__ua {
    my $self = shift;

    # A little weirdness here. I haven't been able to tell Dancer to
    # set an expiration date on its cookie though I can do so with Plack.
    # Because of that, LWP::UA doesn't save the dancer cookie and the login
    # session disappears.
    my $ua = LWP::UserAgent->new(
	agent      => "yavmmc/$VERSION",
	cookie_jar => HTTP::Cookies->new(
	    file           => $self->options->{'cookie-jar'},
	    autosave       => 1,
	    ignore_discard => 1,
	),
    );

    #$ua->cookie_jar->load;
    #debug "Loaded cookies: ", $ua->cookie_jar->as_string;

    return $ua;
}

sub _ssl {
    my $self = shift;

    $self->options->{ssl} ? 1 : 0;
}

sub _port {
    my $self = shift;

    return $self->options->{port} ? $self->options->{port} : 80;
}

sub _host {
    my $self = shift;

    return $self->options->{host} ? $self->options->{host} : 'localhost';
}

method _json_rpc (
    Str     :$path!,            # Path: e.g. /, /json/hosts
    Str     :$method  = 'get'   # get, post, delete. See below --v
	  where { $_ =~ /\A (?:get | post | delete | put) \z /ix },
    HashRef :$args    = {},
    Str     :$content = ''      # Only used by post, put
  ) {

    my $uri = URI->new;
    $uri->scheme($self->_ssl ? 'https' : 'http');
    $uri->authority($self->_host);
    $uri->port($self->_port);
    $uri->path($path);

    debug "uri: ".$uri->as_string;

    my $resp;
    given (lc $method) {
	when ('get')    {
	    $uri->query_form($args);
	    $resp = $self->_ua->get($uri);
	}
	when ('put')    {
	    $uri->query_form($args);
	    my $req = PUT $uri->as_string, Content => $content;
	    $resp = $self->_ua->request($req);
	}
	when ('post')   {
	    $uri->query_form($args);
	    my $req = POST $uri->as_string, Content => $content;
	    $resp = $self->_ua->request($req);
	}
	when ('delete') {
	    $uri->query_form($args);
	    my $req = HTTP::Request->new(DELETE  => $uri->as_string);
	    $resp = $self->_ua->request($req);
	}
	default         {
	    die YAVMM::Error::Parameter(
		error => "Invalid method type: $method");
	}
    }

    if (not $resp) {
	die YAVMM::Error->new(error => "RPC Failure");
    }
    elsif ($resp->is_success) {
	# debug $resp->decoded_content;
	# convert the response from text to JSON.
	return from_json($resp->decoded_content);
    }
    else {
	die YAVMM::Error->new(error => $resp->status_line);
    }
}

sub _json_login {
    my $self = shift;

    $self->_json_rpc(
	path   => '/login',
	method => 'put',
	args   => {
	    user => $self->_username,
	    pass => $self->_password
	}
    );

    $self->_ua->cookie_jar->save;
}

method login {
    try {
	$self->_json_login;
    } catch (YAVMM::Error $err) {
	die "Unable to login: $err\n";
    }

    print "Login successful\n";
}

method logout {
    try {
	$self->_json_rpc(path => '/logout', method => 'put');
#	$self->_ua->cookie_jar->clear;
    } catch (YAVMM::Error $err) {
	die "Log out error: $err\n";
    }

    print "Logout successful\n";
}

# sub _rpc_call {
#     my $self = shift;
#     my $func = shift;
#     my %args = @_;

#     if (not $func) {
# 	die YAVMM::Error::Parameter->new(error => "No RPC function passed");
#     }

#     my $result = SOAP::Lite
# 	-> uri($self->_uri)
# 	    -> proxy($self->_proxy)
# 		-> call($func,
# 			# convert hash to list of SOAP::Data objects
# 			map { SOAP::Data->name($_ => $args{$_}) } keys %args
# 		    );

#     if (not defined $result) {
# 	die YAVMM::Error::RPC->new(
# 	    error  => "Unknown RPC error",
# 	    result => $result
# 	);
#     }
#     elsif ($result->fault) {
# 	die YAVMM::Error::RPC->new(
# 	    error  => "RPC error: ".$result->faultstring,
# 	    result => $result,
# 	);
#     }

#     return $result->result;
# }

sub _build_log {
    my $self = shift;

#    my $log = Log::Dispatch::Config->config(
#	YAVMM::Dispatch::Config->new(settings => $self->settings)
#    );

    return undef;
}

__PACKAGE__->meta->make_immutable;
1;

=head1 NAME

YAVMM::CLI - Class for handling yavmmc cli commands

=head1 SYNOPSOS

 yavmmc [general options] command [cmd options]

=head1 AUTHOR

Randall Smith (perlstalker@vuser.org)

=head1 LICENSE AND COPYRIGHT

Copyright (c) 2010 Randall Smith (perlstalker@vuser.org)
